#include <stdlib.h>
#include <SDL/SDL.h>

#define cast(type) (type)
#define new(type, size) cast(type*)malloc(sizeof(type) * (size))

typedef struct {
    double x, y;
}Vertex;

typedef struct {
    int n;
    Vertex* vert;
}Dane;

typedef struct {
    int l;
    int* buf;
}Buf;

typedef struct {
    int n;
    int* line;
}EMTS;

typedef struct {
    int n;
    int* ver;
}Cycle;

typedef struct {
    int x;
    int y;
}Point;

float scale = 1.;
int mode = 0;
SDL_Surface *screen;
Dane dane;
Buf buf;
EMTS emts;
Cycle euler, hamilton;
int ld = 0, lt = 0, le = 0, leu = 0, lh = 0;

void putPixel(SDL_Surface *surface, int x, int y, int pixel) {
    if(x < 0 || y < 0)
        return;
    if(x >= surface->w || y >= surface->h)
        return;
    int bpp = surface->format->BytesPerPixel;
    /* Here p is the address to the pixel we want to set */
    Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;

    switch(bpp) {
        case 1:
        *p = pixel;
        break;

        case 2:
        *(Uint16 *)p = pixel;
        break;

        case 3:
        if(SDL_BYTEORDER == SDL_BIG_ENDIAN) {
            p[0] = (pixel >> 16) & 0xff;
            p[1] = (pixel >> 8) & 0xff;
            p[2] = pixel & 0xff;
        } else {
            p[0] = pixel & 0xff;
            p[1] = (pixel >> 8) & 0xff;
            p[2] = (pixel >> 16) & 0xff;
        }
        break;

        case 4:
        *(Uint32 *)p = pixel;
        break;
    }
}

Uint32 getPixel(SDL_Surface *surface, int x, int y) {
    int bpp = surface->format->BytesPerPixel;
    /* Here p is the address to the pixel we want to retrieve */
    Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;

    switch(bpp) {
        case 1:
        return *p;

        case 2:
        return *(Uint16 *)p;

        case 3:
        if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
            return p[0] << 16 | p[1] << 8 | p[2];
        else
            return p[0] | p[1] << 8 | p[2] << 16;

        case 4:
        return *(Uint32 *)p;

        default:
        return 0;       /* shouldn't happen, but avoids warnings */
    }
}

void line(SDL_Surface* surface, int x1, int y1, int x2, int y2, int value) {
    int deltax = x2 - x1;
    int deltay = y2 - y1;
    int x;
    int y;
    int incx, incy;
    int error = 0;
    int i;
    if(deltax > 0) {
        incx = 1;
        x = (x1 <= x2 ? x1 : x2);
    } else {
        incx = -1;
        x = (x1 > x2 ? x1 : x2);
        deltax = -deltax;
    }
    if(deltay > 0) {
        incy = 1;
        y = (y1 <= y2 ? y1 : y2);
    } else {
        incy = -1;
        y = (y1 > y2 ? y1 : y2);
        deltay = -deltay;
    }
    if(deltax > deltay) {
        for(i = 1; i <= deltax; ++i) {
            putPixel(surface, x, y, value);
            if(error >= 0) {
                error -= (deltax / 2);
                y += incy;
            }
            error += (deltay / 2);
            x += incx;
        }
    } else {
        for(i = 0; i <= deltay; ++i) {
            putPixel(surface, x, y, value);
            if(error >= 0) {
                error -= (deltay / 2);
                x += incx;
            }
            error += (deltax / 2);
            y += incy;
        }
    }
}

void circlePoints(SDL_Surface *surface,int x0, int y0, int x, int y, unsigned int value) {
    putPixel(surface, x0 + x, y0 + y, value);
    putPixel(surface, x0 + y, y0 + x, value);
    putPixel(surface, x0 + y, y0 - x, value);
    putPixel(surface, x0 + x, y0 - y, value);
    putPixel(surface, x0 - x, y0 - y, value);
    putPixel(surface, x0 - y, y0 - x, value);
    putPixel(surface, x0 - y, y0 + x, value);
    putPixel(surface, x0 - x, y0 + y, value);
}

void circle(SDL_Surface *surface, int x0, int y0, int radius, unsigned int value) {
    int x, y, d;
    int deltaE, deltaSE;

    x = 0;
    y = radius;
    d = 1 - radius;
    deltaE = 3;
    deltaSE = 5 - radius * 2;
    circlePoints(surface, x0, y0, x, y, value);
    while(y > x) {
        if(d < 0) {
            d += deltaE;
            deltaE += 2;
            deltaSE += 2;
            x++;
        } else {
            d += deltaSE;
            deltaE += 2;
            deltaSE += 4;
            x++;
            y--;
        }
        circlePoints(surface, x0, y0, x, y, value);
    }
}

void triangle(SDL_Surface *surface, Point p0, Point p1, Point p2, int value) {
    line(surface, p0.x, p0.y, p1.x, p1.y, value);
    line(surface, p1.x, p1.y, p2.x, p2.y, value);
    line(surface, p0.x, p0.y, p2.x, p2.y, value);
}

void drawFrame() {
    int i, r, x, y, col;

    if(mode == 5)
        for(i = 1; i < hamilton.n; i++)
            line(screen, cast(int)dane.vert[hamilton.ver[i-1]].x,cast(int)dane.vert[hamilton.ver[i-1]].y,
                 cast(int)dane.vert[hamilton.ver[i]].x,cast(int)dane.vert[hamilton.ver[i]].y,0x20ff60);

    if(mode == 4)
        for(i = 1; i < euler.n; i++)
            line(screen, cast(int)dane.vert[euler.ver[i-1]].x,cast(int)dane.vert[euler.ver[i-1]].y,
                 cast(int)dane.vert[euler.ver[i]].x,cast(int)dane.vert[euler.ver[i]].y,0xffff20);

    if(mode == 3)
        for(i = 0; i < emts.n; i+=2)
            line(screen,
                 cast(int)dane.vert[emts.line[i]].x, cast(int)dane.vert[emts.line[i]].y,
                 cast(int)dane.vert[emts.line[i+1]].x, cast(int)dane.vert[emts.line[i+1]].y, 0x00a0ff);

    if(mode == 2)
        for(i = 0; i < buf.l; i+=3) {
            Point p0;
            Point p1;
            Point p2;
            p0.x = cast(int)dane.vert[buf.buf[i]].x;
            p0.y = cast(int)dane.vert[buf.buf[i]].y;
            p1.x = cast(int)dane.vert[buf.buf[i+1]].x;
            p1.y = cast(int)dane.vert[buf.buf[i+1]].y;
            p2.x = cast(int)dane.vert[buf.buf[i+2]].x;
            p2.y = cast(int)dane.vert[buf.buf[i+2]].y;
            triangle(screen, p0, p1, p2, 0xff5040);
        }

    if(mode >= 1) {
        for(i = 0; i < dane.n; i++) {
			r = 4;

			for (x = - r; x <= r; ++x) {
				for (y = -r; y <= r; ++y) {
					if (x*x + y*y > r*r+1) continue;

					col = 255 * (x*x+y*y)/r/r;
					if (col > 255) col = 255;
					if (col < 0) col = 0;

					col = 255 - col;
					if (col < 32) continue;		// bgcol

					putPixel(screen, x+dane.vert[i].x, y+dane.vert[i].y, col + col * 256 + col * 65536);
				}
			}
        }
    }
}

void loadDane(char* filePath) {
    int i;
    FILE* fp;

    fp = fopen(filePath, "r");
    if (!fp) {
        printf("Otwarcie pliku nie powiodlo sie.\n");
        return;
    }

    fscanf(fp, "%d", &dane.n);

    dane.vert = new(Vertex, dane.n);

    for (i = 0; i < dane.n; i++)
        fscanf(fp, "%lf %lf", &dane.vert[i].x, &dane.vert[i].y);
    fclose(fp);
    mode = 1;
    ld = 1;
}

void saveDane(char* filePath) {
    int i;
    FILE* fp;

    fp = fopen(filePath, "w");
    if (!fp) {
        printf("Otwarcie pliku nie powiodlo sie.\n");
        return;
    }

    fprintf(fp, "%d\n", dane.n);

    for (i = 0; i < dane.n; i++)
        fprintf(fp, "%lf %lf\n", dane.vert[i].x, dane.vert[i].y);
    fclose(fp);
}

void loadTriangulacja(char* filePath) {
    int i;
    FILE* fp;

    fp = fopen(filePath, "r");
    if (!fp) {
        printf("Otwarcie pliku nie powiodlo sie.\n");
        return;
    }

    fscanf(fp, "%d", &buf.l);

    buf.buf = (int*)realloc(buf.buf,sizeof(int)* buf.l);

    for (i = 0; i < buf.l; i+=3)
        fscanf(fp, "%d %d %d\n", &buf.buf[i],&buf.buf[i+1],&buf.buf[i+2]);
    fclose(fp);
    mode = 2;
    lt = 1;
}

void loadEMTS(char* filePath) {
    int i,j,a,b;
    FILE* fp;

    fp = fopen(filePath, "r");
    if (!fp) {
        printf("Otwarcie pliku nie powiodlo sie.\n");
        return;
    }

    emts.line = (int*)realloc(emts.line, sizeof(int)* 12 * dane.n);
	emts.n = 0;

    fscanf(fp, "%d\n", &a);
    for (i = 0; i < a; i++) {
        fscanf(fp, "%d", &b);
        for(j = 0; j < b; j++) {
            emts.line[emts.n] = i;
            fscanf(fp, " %d", &emts.line[emts.n + 1]);
            (emts.n)+=2;
        }
        fscanf(fp,"\n");
    }
    fclose(fp);
    for(i = 0; i < emts.n; i+=2)
        printf("%d %d\n", emts.line[i], emts.line[i+1]);
    fflush(stdout);
    mode = 3;
    le = 1;
}

void loadEuler(char* filePath) {
    int i;
    FILE* fp;

    fp = fopen(filePath, "r");
    if (!fp) {
        printf("Otwarcie pliku nie powiodlo sie.\n");
        return;
    }

    fscanf(fp, "%d\n", &euler.n);

    euler.ver = (int*)realloc(euler.ver, sizeof(int)*euler.n);

    for (i = 0; i < euler.n; i++)
        fscanf(fp, "%d ", &euler.ver[i]);
    fclose(fp);
    mode = 4;
    leu = 1;
}

void loadHamilton(char* filePath) {
    int i;
    FILE* fp;

    fp = fopen(filePath, "r");
    if (!fp) {
        printf("Otwarcie pliku nie powiodlo sie.\n");
        return;
    }

    fscanf(fp, "%d\n", &hamilton.n);

    hamilton.ver = (int*)realloc(hamilton.ver, sizeof(int) * hamilton.n);

    for (i = 0; i < hamilton.n; i++)
        fscanf(fp, "%d ", &hamilton.ver[i]);
    fclose(fp);
    mode = 5;
    lh = 1;
}

void mode0(){
    ld = 0, lt = 0, le = 0, leu = 0, lh = 0;
}

void mode1(){
    if(ld)
       mode = 1;
    else
       loadDane("dane.in");
}

void mode2(){
    if(lt)
       mode = 2;
    else{
        rename("dane.in", "dane.in.old");
        saveDane("dane.in");

#ifdef WIN32
        system("c:\\prog\\mpi\\mpd\\bin\\MPIRun.exe -np 12 main.exe dane.in");
#else
        system("/usr/local/misc/mpich-r/bin/mpirun -np 2 ./main dane.in");
#endif
        loadTriangulacja("triangulacja.out");
	}
}

void mode3(){
    if(le)
       mode = 3;
    else
    loadEMTS("emts.out");
}

void mode4(){
    if(leu)
       mode = 4;
    else
    loadEuler("euler.out");
}

void mode5(){
    if(lh)
       mode = 5;
    else
    loadHamilton("hamilton.out");
}

void keyDown(SDL_KeyboardEvent e) {
    if(e.keysym.sym == SDLK_ESCAPE)
        exit(0);

    /*if(e.keysym.sym == SDLK_s) {
        rename("dane.in", "dane.in.old");
        saveDane("dane.in");
    }*/

    if(e.keysym.sym == SDLK_RIGHT){
        mode++;
        if(mode > 5) mode = 5;
    }
    if(e.keysym.sym == SDLK_LEFT){
        mode--;
        if(mode < 0) mode = 0;
    }

    if(e.keysym.sym == 'r'){
		mode0();
		mode = 1;

#ifdef WIN32
        system("randGen.exe");
#else
        system("./randGen");
#endif
    }

	if(mode == 0) mode0();
    if(mode == 1) mode1();
    if(mode == 2) mode2();
    if(mode == 3) mode3();
    if(mode == 4) mode4();
    if(mode == 5) mode5();
}

void addPoint(int x, int y) {
    dane.vert = (Vertex*)realloc(dane.vert, sizeof(Vertex)*(dane.n+1));
    dane.vert[dane.n].x = x;
    dane.vert[dane.n].y = y;
    (dane.n)++;
}

void mouseUp(SDL_MouseButtonEvent e) {
    if(e.button == SDL_BUTTON_LEFT) {
    }
}

void mouseDown(SDL_MouseButtonEvent e) {
	int i, best, bdist = 100000, dist;

    if(e.button == SDL_BUTTON_LEFT) {
        if(mode == 1) {
            addPoint(e.x, e.y);
			mode0();
		}
    }
    if(e.button == SDL_BUTTON_RIGHT) {
		if (1 == mode) {
			for (i = 0; i < dane.n; ++i) {
				dist = (dane.vert[i].x - e.x)*(dane.vert[i].x - e.x) + (dane.vert[i].y - e.y)*(dane.vert[i].y - e.y);
				if (dist < bdist) {
					bdist = dist;
					best = i;
				}
			}

			if (bdist <= 10*10) {
				dane.vert[best] = dane.vert[dane.n-1];
				--dane.n;
				mode0();
			}
		}
    }
}

void mouseMotion(SDL_MouseMotionEvent e) {
	e = e;
}

int main(int argc, char* argv[]) { //Our main program
    SDL_Event event; //Events
    int done = 0; //Not done before we've started...

	argc = argc;
	argv = argv;

    if(SDL_Init(SDL_INIT_VIDEO) < 0) { //Could we start SDL_VIDEO?
        exit(1);
    }

    loadDane("dane.in");

    atexit(SDL_Quit); //Now that we're enabled, make sure we cleanup
    SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
    screen = SDL_SetVideoMode(800, 600, 32, SDL_HWSURFACE);

    while(!done) { //While program isn't done
        while(SDL_PollEvent(&event)) { //Poll events
            switch(event.type) { //Check event type
                case SDL_QUIT: //User hit the X (or equivelent)
                    done = 1; //Make the loop end
                    break; //We handled the event
                case SDL_KEYDOWN:
                    keyDown(event.key);
                    break;
                case SDL_MOUSEBUTTONDOWN:
                    mouseDown(event.button);
                    break;
                case SDL_MOUSEBUTTONUP:
                    mouseUp(event.button);
                    break;
                case SDL_MOUSEMOTION:
                    mouseMotion(event.motion);
                    break;
                default:
                    break;
            } //Finished with current event
        } //Done with all events for now
        SDL_LockSurface(screen);
        memset(screen->pixels, 32, 800*600*sizeof(unsigned int));
        drawFrame();
        SDL_UnlockSurface(screen);
        SDL_UpdateRect(screen,0,0,0,0);
    } //Program done, exited
    return 0;
}

